<?php

defined('APP_URL') or define('APP_URL', 'http://fireoauth.local/server.php');
defined('APP_KEY') or define('APP_KEY', 'your app key');
defined('APP_SECRET') or define('APP_SECRET', 'your app secret');
defined('SALT') or define('SALT', '1409852095');//盐值
defined('VALID_TIME') or define('VALID_TIME', 10);//链接有效时间

class fireoauth_server{
    
    private $redirect_uri;
    
    public function __construct() {
        if (empty($_GET['auth'])
                || empty($_GET['time'])
                || empty($_GET['state'])
                || empty($_GET['request_type']))
            unauth('empty');
        
        if (empty($_GET['request_type']))
            badrequest ();
        
        if (!empty($_GET['timezone']))
            date_default_timezone_set($_GET['timezone']);
        
        $time = intval($_GET['time']);
        $time_md5 = md5(SALT.$time);
        if (($time_md5 != $_GET['state']) || (time()-$time > VALID_TIME))
            unauth('timeout');
        
        session_id($_GET['auth']);
        session_start();
        
        switch ($_GET['request_type']) {
            case 'code':
                if (empty($_GET['key']) || empty($_GET['secret'])
                        || ($_GET['key'] != APP_KEY) || ($_GET['secret'] != APP_SECRET))
                    unauth('code request error');
                break;
            case 'token':
                if (empty($_GET['code']) || ($_GET['code'] != $this->code()))
                    unauth('token request error');
                break;
            case 'refresh':
                if (empty($_GET['key']) || empty($_GET['secret'])
                        || ($_GET['key'] != APP_KEY) || ($_GET['secret'] != APP_SECRET))
                    unauth('refresh request error');
                break;
            default:
        }
        
        $this->redirect_uri = $_GET['redirect_uri'];
        
        
        $this->authorize();
    }
    
    private function authorize(){
        switch ($_GET['request_type']) {
            case 'code':
                $code = random(6);
                $_SESSION['oauth_code'] = $code;
                echo $this->encode(array(
                    'code'=>$code
                ));
                break;
            case 'token':
                $access_token = random(18);
                $_SESSION['oauth_access_token'] = $access_token;
                echo $this->encode(array(
                    'access_token'=>$access_token
                ));
                break;
            case 'refresh':
                echo $this->encode(array(
                    'code'=>$_SESSION['oauth_code'],
                    'access_token'=>$_SESSION['oauth_access_token']
                ));
                break;
            default:                
        }
        die();
    }
    
    private function code(){
        return !empty($_SESSION['oauth_code'])?$_SESSION['oauth_code']:'';
    }
    
    private function accessToken(){
        return !empty($_SESSION['oauth_access_token'])?$_SESSION['oauth_access_token']:'';
    }
    
    private function encode($ret = array()){
        return json_encode($ret);
    }
}

class fireoauth{
    
    private $redirect_uri;
    
    public function __construct() {
        $this->redirect_uri = url();
    }
    
    public function isAuthorized(){
        return (bool)$this->accessToken()?true:$this->refreshAccessToken();
    }
    
    public function authorize(){
        if (!$access_token = $this->accessToken()){
            if (!$code = $this->code()){
                if (!$code = $this->getCode())
                    return false;
                redirect($this->redirect_uri);
            }
            $access_token = $this->getAccessToken();
        }
        return (bool)$access_token;
    }
    
    private static function genAuthorizeUrl(){
        return APP_URL;
    }
    
    public static function genSessionId(){
        if (empty($_COOKIE['oauth_auth'])){
            $key = uniqid(random(6),true);
            $session_id =  md5(SALT.$key);
            setcookie('oauth_auth',$session_id);
        }else{
            $session_id = $_COOKIE['oauth_auth'];
        }
        return $session_id;
    }
    
    private function code(){
        return !empty($_COOKIE['oauth_code'])?$_COOKIE['oauth_code']:'';
    }
    
    private function accessToken(){
        return !empty($_COOKIE['oauth_access_token'])?$_COOKIE['oauth_access_token']:'';
    }
    
    /**
     * 获取授权的URL
     * 
     * @param string $redirectUri (授权成功后的回调页面地址，即第三方应用的url地址)
     * @return string
     */
    private function getAuthorizeURL($redirect_uri,$type)
    {
        $auth = self::genSessionId();// 随机生成唯一串防CSRF攻击
        $time = time();
        $time_md5 = md5(SALT.$time);
        $params = array(
            'timezone'=>date_default_timezone_get(),
            'redirect_uri' => $redirect_uri,
            'auth' => $auth,
            'time'=>$time,
            'state'=>$time_md5,
            'request_type'=>$type,
        );
        switch ($type) {
            case 'code':
                $params += array(
                     'key'=>APP_KEY,
                     'secret'=>APP_SECRET,
                );
                break;
            case 'token':
                $params += array(
                    'code'=>$this->code()
                );
                break;
            case 'refresh':
                $params += array(
                     'key'=>APP_KEY,
                     'secret'=>APP_SECRET,
                );
                break;
            default:
                break;
        }
        return self::genAuthorizeUrl() . '?' . http_build_query($params);
    }

    private function getCode(){
        $auth_url = $this->getAuthorizeURL($this->redirect_uri,'code');
        $result = $this->access($auth_url);
        echo $result;
        if ($code = $this->decode($result,'code')){
            setcookie('oauth_code',$code);
        }
        return $code;
    }
    
    private function getAccessToken(){
        $auth_url = $this->getAuthorizeURL($this->redirect_uri,'token');
        $result = $this->access($auth_url);
        if ($access_token = $this->decode($result,'access_token')){
            if (setcookie('oauth_access_token',$access_token)){
                setcookie('oauth_code',null,time()-1);
            }
        }
        return $access_token;
    }
    
    private function refreshAccessToken(){
        $auth_url = $this->getAuthorizeURL($this->redirect_uri,'refresh');
        $result = $this->access($auth_url);
        if ($access_token = $this->decode($result,'access_token')){
            setcookie('oauth_access_token',$access_token);
        }
        return $access_token;
    }
    
    private function access($url){
        $result = uc_fopen($url);
        if ($error = $this->decode($result,'error')){
            error($error);
        }
        return $result;
    }
    
    private function decode($json,$key = null){
        if (!is_array($json = json_decode($json,true)))
            return false;
        if ($key && empty($json[$key]))
            return false;
        return $key?$json[$key]:$json;
    }
}

function error($message = ''){
    send_http_status(500);
    die($message?$message:'');
}

function badrequest($message = ''){
    send_http_status(400);
    die($message?json_encode(array('error'=>$message)):'');
}

function unauth($message = ''){
    send_http_status(401);
    die($message?json_encode(array('error'=>$message)):'');
}

/**
 * 发送HTTP状态
 * @param integer $code 状态码
 * @return void
 */
function send_http_status($code) {
    static $_status = array(
        // Informational 1xx
        100 => 'Continue',
        101 => 'Switching Protocols',
        // Success 2xx
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        // Redirection 3xx
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Moved Temporarily ', // 1.1
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        // 306 is deprecated but reserved
        307 => 'Temporary Redirect',
        // Client Error 4xx
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        // Server Error 5xx
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        509 => 'Bandwidth Limit Exceeded'
    );
    if(isset($_status[$code])) {
        header('HTTP/1.1 '.$code.' '.$_status[$code]);
        // 确保FastCGI模式下正常
        header('Status:'.$code.' '.$_status[$code]);
    }
}

function url(){
    $url = (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443') ? 'https://' : 'http://';
    $url .= isset($_SERVER['SERVER_NAME'])?$_SERVER['SERVER_NAME']:$_SERVER['HTTP_HOST'];
    $url .= isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : urlencode($_SERVER['PHP_SELF']) . '?' . urlencode($_SERVER['QUERY_STRING']);
    return $url;
}

/**
 * URL重定向
 * @param string $url 重定向的URL地址
 * @param integer $time 重定向的等待时间（秒）
 * @param string $msg 重定向前的提示信息
 * @return void
 */
function redirect($url, $time=0, $msg='') {
    //多行URL地址支持
    $url        = str_replace(array("\n", "\r"), '', $url);
    if (empty($msg))
        $msg    = "系统将在{$time}秒之后自动跳转到{$url}！";
    if (!headers_sent()) {
        // redirect
        if (0 === $time) {
            header('Location: ' . $url);
        } else {
            header("refresh:{$time};url={$url}");
            echo($msg);
        }
        exit();
    } else {
        $str    = "<meta http-equiv='Refresh' content='{$time};URL={$url}'>";
        if ($time != 0)
            $str .= $msg;
        exit($str);
    }
}

function uc_fopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE) {
	$return = '';
	$matches = parse_url($url);
	!isset($matches['host']) && $matches['host'] = '';
	!isset($matches['path']) && $matches['path'] = '';
	!isset($matches['query']) && $matches['query'] = '';
	!isset($matches['port']) && $matches['port'] = '';
	$host = $matches['host'];
	$path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
	$port = !empty($matches['port']) ? $matches['port'] : 80;
	if($post) {
		$out = "POST $path HTTP/1.0\r\n";
		$out .= "Accept: */*\r\n";
		//$out .= "Referer: $boardurl\r\n";
		$out .= "Accept-Language: zh-cn\r\n";
		$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
		$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
		$out .= "Host: $host\r\n";
		$out .= 'Content-Length: '.strlen($post)."\r\n";
		$out .= "Connection: Close\r\n";
		$out .= "Cache-Control: no-cache\r\n";
		$out .= "Cookie: $cookie\r\n\r\n";
		$out .= $post;
	} else {
		$out = "GET $path HTTP/1.0\r\n";
		$out .= "Accept: */*\r\n";
		//$out .= "Referer: $boardurl\r\n";
		$out .= "Accept-Language: zh-cn\r\n";
		$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
		$out .= "Host: $host\r\n";
		$out .= "Connection: Close\r\n";
		$out .= "Cookie: $cookie\r\n\r\n";
	}

	if(function_exists('fsockopen')) {
		$fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
	} elseif (function_exists('pfsockopen')) {
		$fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
	} else {
		$fp = false;
	}

	if(!$fp) {
		return '';
	} else {
		stream_set_blocking($fp, $block);
		stream_set_timeout($fp, $timeout);
		@fwrite($fp, $out);
		$status = stream_get_meta_data($fp);
		if(!$status['timed_out']) {
			while (!feof($fp)) {
				if(($header = @fgets($fp)) && ($header == "\r\n" ||  $header == "\n")) {
					break;
				}
			}

			$stop = false;
			while(!feof($fp) && !$stop) {
				$data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
				$return .= $data;
				if($limit) {
					$limit -= strlen($data);
					$stop = $limit <= 0;
				}
			}
		}
		@fclose($fp);
		return $return;
	}
}

function random($len = 6, $format = 'ALL') {
	switch (strtoupper($format)) {
		case 'ALL' :
			$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
			break;
		case 'CHAR' :
			$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
			break;
		case 'NUMBER' :
			$chars = '0123456789';
			break;
		default :
			$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
			break;
	}
	$string = "";
	while ( strlen ( $string ) < $len )
		$string .= substr ( $chars, (mt_rand () % strlen ( $chars )), 1 );
	return $string;
}